﻿using System;
using System.Collections;
using System.Linq;
using System.Net;
using System.Xml.Linq;
using PeoplesJukebox.Web.Entities;
using PeoplesJukebox.Web.Enums;

namespace PeoplesJukebox.Web.ServiceClients.Bases
{
    /// <summary>
    /// Base class for clients of RESTful WCF services
    /// </summary>
    public abstract class ServiceClientBase
    {
        public delegate void SearchCompleteEventHandler(IEnumerable results);
        public event SearchCompleteEventHandler SearchComplete;

        private WebClient _client;

        protected abstract string BaseAddress { get; }

        /// <summary>
        /// Returns the client
        /// </summary>
        protected WebClient Client
        {
            get
            {
                if (this._client == null)
                {
                    this._client = new WebClient();
                    this.AddEventHandlers();
                }
                return _client;
            }
        }

        /// <summary>
        /// Cancels current request
        /// </summary>
        public void CancelRequest()
        {
            this.Client.CancelAsync();
        }

        /// <summary>
        /// Adds client event handlers
        /// </summary>
        protected virtual void AddEventHandlers()
        {
            if (this._client == null)
            {
                this._client = new WebClient();
            }
            this._client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(SearchDownloadComplete);
        }

        /// <summary>
        /// Removes client event handlers
        /// </summary>
        protected void RemoveEventHandlers()
        {
            this._client.DownloadStringCompleted -= SearchDownloadComplete;
        }
        
        /// <summary>
        /// Returns a fully-qualified URI
        /// </summary>
        /// <param name="relativeAddress">Relative path</param>
        /// <param name="args">Path arguments</param>
        /// <returns>URI</returns>
        protected Uri GetUri(string relativeAddress, params object[] args)
        {
            string address = string.Format(string.Concat(this.BaseAddress, relativeAddress), args);
            return new Uri(address, UriKind.Relative);
        }

        /// <summary>
        /// Starts asynchronous download call
        /// </summary>
        /// <param name="relativeAddress">Relative path</param>
        /// <param name="args">Path arguments</param>
        protected void StartDownload(string relativeAddress, params object[] args)
        {
            this.StartDownload(this.GetUri(relativeAddress, args));
        }

        /// <summary>
        /// Starts asynchronous download call
        /// </summary>
        /// <param name="downloadUri">URI</param>
        protected void StartDownload(Uri downloadUri)
        {
            if (this.Client.IsBusy)
            {
                this.Client.CancelAsync();
            }
            this.Client.DownloadStringAsync(downloadUri);
        }

        /// <summary>
        /// Notifies subscribers when asynchronous download completes
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">EventArgs</param>
        protected virtual void SearchDownloadComplete(object sender, DownloadStringCompletedEventArgs e)
        {
            if (SearchComplete != null && e.Error == null)
            {
                IEnumerable mediaItems = ParseDownloadResults(e);
                SearchComplete(mediaItems);
            }
        }

        /// <summary>
        /// Parses XML download results into <see cref="MediaItem"/> collection
        /// </summary>
        /// <param name="e">EventArgs</param>
        /// <returns>MediaItem collection</returns>
        protected IEnumerable ParseDownloadResults(DownloadStringCompletedEventArgs e)
        {
            return ParseDownloadResults(e.Result);
        }

        /// <summary>
        /// Parses XML download results into <see cref="MediaItem"/> collection
        /// </summary>
        /// <param name="xmlResults">XML string results</param>
        /// <returns>MediaItem collection</returns>
        protected virtual IEnumerable ParseDownloadResults(string xmlResults)
        {
            XDocument results = XDocument.Parse(xmlResults);
            XName itemName = XName.Get("MediaItem", Namespace.MediaItemBases);
            
            var mediaItems = from mediaItem in results.Descendants(itemName)
                             orderby (string)mediaItem.Element(XName.Get("Name", Namespace.MediaItemBases))
                             select new MediaItem
                             {
                                 Id = (int)mediaItem.Element(XName.Get("Id", Namespace.MediaItemBases)),
                                 Name = (string)mediaItem.Element(XName.Get("Name", Namespace.MediaItemBases)),
                                 AlbumName = (string)mediaItem.Element(XName.Get("AlbumName", Namespace.MediaItemBases)),
                                 ArtistName = (string)mediaItem.Element(XName.Get("ArtistName", Namespace.MediaItemBases)),
                                 ItemType = (ItemType)Enum.Parse(typeof(ItemType), (string)mediaItem.Element(XName.Get("ItemType", Namespace.MediaItemBases)), true)
                             };
            return mediaItems;
        }

        /// <summary>
        /// Known namespaces
        /// </summary>
        protected struct Namespace
        {
            public const string MediaItemBases = "http://schemas.datacontract.org/2004/07/PeoplesJukebox.Core.MediaItems.Bases";
            public const string MediaItems = "http://schemas.datacontract.org/2004/07/PeoplesJukebox.Core.MediaItems";
        }
    }
}